using System;
using System.Collections.Generic;
using System.Text;
using Calc.Operations;
using Calc.Operations.Functions;

namespace Calc.Execution
{
	public class ExecutionContext
	{
		protected Dictionary<string, double> _variables;
		protected Dictionary<string, Function> _functions;

		public Dictionary<string, double> Variables
		{
			get { return _variables; }
		}

		public Dictionary<string, Function> Functions
		{
			get { return _functions; }
		}

		public ExecutionContext()
		{
			_variables = new Dictionary<string, double>();
			_functions = new Dictionary<string, Function>();
		}

		/// <summary>
		/// Sets the value for a given variable
		/// </summary>
		/// <param name="name">The name of the variable</param>
		/// <param name="value">The value to be assigned</param>
		/// <returns>The previous value of the variable, if the specified variable 
		/// already existed; otherwise double.MinValue.</returns>
		public virtual double SetVariable(string name, double value)
		{
			double ret = double.MinValue;

			if ( _variables.ContainsKey(name) )
			{
				ret = _variables[name];
			}

			_variables[name] = value;

			return ret;
		}

		/// <summary>
		/// Retrieves the value for a given variable
		/// </summary>
		/// <param name="name">The name of the variable</param>
		/// <returns>The value for the specified variable, or double.MinValue if
		/// that variable is undefined</returns>
		public virtual double GetVariable(string name)
		{
			if ( _variables.ContainsKey(name) )
			{
				return _variables[name];
			}
			else
			{
				throw new ArgumentException("The variable " + name + " does not exist", "name");
			}
		}

		/// <summary>
		/// Retrieves whether or not a given variable exists
		/// </summary>
		/// <param name="name">The name of the variable</param>
		/// <returns>true if the variable is declared; otherwise false</returns>
		public virtual bool IsVariableDeclared(string name)
		{
			return _variables.ContainsKey(name);
		}

		/// <summary>
		/// Adds a new function to the running context, or overrides and existing function
		/// </summary>
		/// <param name="function"></param>
		public virtual void CreateFunction(Function function)
		{
			if ( function == null )
			{
				throw new ArgumentNullException("function", "function can not be null");
			}

			_functions[function.Name] = function;
		}

		/// <summary>
		/// Checks whether or not a function is defined
		/// </summary>
		/// <param name="function">The function call to check</param>
		/// <returns>True if the function is defined, false otherwise</returns>
		public virtual bool IsFunctionDefined(Function function)
		{
			if ( function == null )
			{
				throw new ArgumentNullException("function", "function can not be null");
			}

			return IsFunctionDefined(function.Name);
		}

		/// <summary>
		/// Checks whether or not a function is defined
		/// </summary>
		/// <param name="name">The name of the function to check</param>
		/// <returns>True if the function is defined, false otherwise</returns>
		public virtual bool IsFunctionDefined(string name)
		{
			return _functions.ContainsKey(name);
		}

		/// <summary>
		/// Returns the result of executing the given function within a local context
		/// </summary>
		/// <param name="function"></param>
		/// <returns></returns>
		public virtual double ExecuteFunction(Function function)
		{
			if ( function == null )
			{
				throw new ArgumentNullException("function", "function can not be null");
			}

			if ( !_functions.ContainsKey(function.Name) )
			{
				throw new ArgumentException(function.Name + " is not defined", "function");
			}

			// resolve variables

			Function functionDef = _functions[function.Name];

			List<string> missingVars = new List<string>();

			Dictionary<string, double> varResolutions = new Dictionary<string, double>();

			int i = ResolveFunctionParameters(function, functionDef, missingVars, varResolutions);

			if ( i < function.Variables.Count )
				throw new ExecutionException("There are too many parameters for the function call");

			CheckForMissingParameters(missingVars);

			// create new EC for the function call

			ExecutionContext context = CreateFunctionCallContext(varResolutions);

			return functionDef.GetValue(context);
		}

		protected static void CheckForMissingParameters(List<string> missingVars)
		{
			if ( missingVars.Count > 0 )
			{
				if ( missingVars.Count > 1 )
				{
					string message = "The following parameters are missing: ";

					bool first = true;
					foreach ( string varName in missingVars )
					{
						if ( first )
							first = false;
						else
							message += ", ";

						message += varName;
					}

					throw new ExecutionException(message);
				}
				else
				{
					string message = "The parameter \"" + missingVars[0] + "\" is missing";

					throw new ExecutionException(message);
				}
			}
		}

		protected int ResolveFunctionParameters(Function function, Function functionDef, List<string> missingVars, Dictionary<string, double> varResolutions)
		{
			int i;
			for ( i = 0; i < functionDef.Variables.Count; i++ )
			{
				Variable var = functionDef.Variables[i] as Variable;

				if ( var == null )
					throw new ExecutionException("The parameter list of a function must be all variables");

				if ( i >= function.Variables.Count )
				{
					missingVars.Add(var.Name);
				}
				else
				{
					double value = 0.0d;

					if ( function.Variables[i] is Function )
						value = ExecuteFunction(function.Variables[i] as Function);
					else 
						value = function.Variables[i].GetValue(this);

					varResolutions.Add(var.Name, value);
				}
			}
			return i;
		}

		protected virtual ExecutionContext CreateFunctionCallContext(Dictionary<string, double> varResolutions)
		{
			ExecutionContext context = new ExecutionContext();

			// Copy any variables in this context...
			foreach ( KeyValuePair<string, double> kvp in this._variables )
			{
				context.Variables[kvp.Key] = kvp.Value;
			}

			// then add the local parameter resolutions
			foreach ( KeyValuePair<string, double> kvp in varResolutions )
			{
				context.Variables[kvp.Key] = kvp.Value;
			}

			// now carry over the existing functions
			foreach ( KeyValuePair<string, Function> kvp in this._functions )
			{
				context.Functions[kvp.Key] = kvp.Value;
			}

			return context;
		}

		/// <summary>
		/// Returns the value of the ANSWER variable
		/// </summary>
		public double Answer
		{
			get
			{
				if ( _variables.ContainsKey(ExecutionEngine.ANSWER_VARIABLE))
				{
					return _variables[ExecutionEngine.ANSWER_VARIABLE];
				}

				return 0.0d;
			}
		}

		public void InitBuiltInFunctions()
		{
			_functions.Add(Abs.NAME, new Abs());
			_functions.Add(Acos.NAME, new Acos());
			_functions.Add(Asin.NAME, new Asin());
			_functions.Add(Atan.NAME, new Atan());
		}

		public void InitBuiltInConstants()
		{
			_variables.Add("PI", Math.PI);
			_variables.Add("E", Math.E);
		}
	}
}
